/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jbpm.pvm.internal.cmd;

import java.util.Date;

import org.jbpm.api.JbpmException;
import org.jbpm.api.cmd.Environment;
import org.jbpm.api.job.Job;
import org.jbpm.internal.log.Log;
import org.jbpm.pvm.internal.env.EnvironmentImpl;
import org.jbpm.pvm.internal.env.JobContext;
import org.jbpm.pvm.internal.job.JobImpl;
import org.jbpm.pvm.internal.jobexecutor.JobExceptionHandler;
import org.jbpm.pvm.internal.session.DbSession;
import org.jbpm.pvm.internal.tx.Transaction;



/**
 * @author Tom Baeyens
 */
public class ExecuteJobCmd extends AbstractCommand<Job> {

  private static final long serialVersionUID = 1L;

  private static final Log log = Log.getLog(ExecuteJobCmd.class.getName());
  
  protected Long jobDbid;

  public ExecuteJobCmd(String jobId) {
    if (jobId==null) {
      throw new JbpmException("jobId is null");
    }
    jobDbid = Long.parseLong(jobId);
  }

  public ExecuteJobCmd(Long jobDbid) {
    this.jobDbid = jobDbid;
  }

  public Job execute(Environment environmentInterface) throws Exception {
    EnvironmentImpl environment = (EnvironmentImpl) environmentInterface;
  	DbSession dbSession = environment.get(DbSession.class);
  	if (dbSession==null) {
  	  throw new JbpmException("no db-session configured");
  	}
    JobImpl<?> job = (JobImpl<?>) dbSession.get(JobImpl.class, jobDbid);

  	// in case of decision jobs, the job might have been deleted
  	// before we execute it (they are in a list)
  	if (job != null) {
  	    JobContext jobContext = new JobContext(job);
        environment.setContext(jobContext);
	    try {
	      log.debug("executing job "+job+"...");
	      job.execute(environment);
	      log.debug("executed job "+job);

	      // if this job is locked too long, it could be unlocked by the lockmonitor and 
	      // executed by another thread.
	      Date lockExpirationDate = job.getLockExpirationTime();
	      // can be null if it was rescheduled
	      if (lockExpirationDate != null) {
		      long lockExpiration = lockExpirationDate.getTime();
		      long currentTime = System.currentTimeMillis();
		      if (currentTime>lockExpiration) {
		        throw new JbpmException("job took too long: lock expired "+(currentTime-lockExpiration)+"ms ago");
		      }
	      }
	    } catch (Exception exception) {
	      log.error("exception while executing '"+job+"'", exception);
	      handleJobExecutionException(environment, job, exception);
	    } finally {
	      environment.removeContext(jobContext);
	    }

  	} else {
  		log.debug("job " + jobDbid + " no longer exists");
  	}
    return job;
  }

  /** This transaction will be marked for rollback.  A command will be associated with the 
   * Transaction.EVENT_AFTERCOMPLETION (after the job locks of the current transaction are 
   * released).  Then the command will update the job with the exception details in a separate 
   * transaction. */
  protected void handleJobExecutionException(Environment environment, JobImpl<?> job, Exception exception) {
    Transaction transaction = environment.get(Transaction.class);
    CommandService commandService = (CommandService) environment.get(CommandService.NAME_NEW_TX_REQUIRED_COMMAND_SERVICE);
    JobExceptionHandler jobExceptionHandler = new JobExceptionHandler(job.getDbid(), exception, commandService);
    transaction.registerSynchronization(jobExceptionHandler);
    
    if (exception instanceof RuntimeException) {
      throw (RuntimeException) exception;
    }
    throw new JbpmException("job failed: "+exception.getMessage(), exception);
  }
}
